package org.example.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.example.api.ArticleApi;
import org.example.common.Result;
import org.example.mapper.ArticleMapper;
import org.example.mapper.CategoriesMapper;
import org.example.mapper.TagMapper;
import org.example.model.Article;
import org.example.model.Categories;
import org.example.model.Tag;
import org.example.utils.RedisUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Component
@Service(interfaceClass = ArticleApi.class, version = "1.0.0", timeout = 35000)
public class ArticleService implements ArticleApi {

    private static final Object PAGE_ARTICLE_BY_USER_ID = new Object();
    private static final Object GET_ARTICLE_BY_ID = new Object();
    private static final Object QUERY_ARTICLE_BY_USER_ID = new Object();
    private static final Object PAGE_ARTICLE_BY_USER_ID_AND_CATEGORIES_ID = new Object();

    @Autowired
    private ArticleMapper articleMapper;
    @Autowired
    private CategoriesMapper categoriesMapper;
    @Autowired
    private TagMapper tagMapper;
    @Autowired
    private RedisUtil redisUtil;

    @Override
    public Result<List<Article>> pageArticleByUserId(Long userId, Long page, Long size) {
        // user:{userId}:page:{page}
        String key = "user:" + userId + ":page:" + page;
        List<Article> records = redisUtil.getObject(key);
        if (CollectionUtils.isEmpty(records)) {
            synchronized (PAGE_ARTICLE_BY_USER_ID) {
                records = redisUtil.getObject(key);
                if (CollectionUtils.isEmpty(records)) {
                    LambdaQueryWrapper<Article> lambdaQueryWrapper = new LambdaQueryWrapper<Article>().select(Article.class, i -> !i.getProperty().equals("context")).eq(Article::getUserId, userId);
                    records = getArticleList(page, size, key, lambdaQueryWrapper);
                }
            }
        }
        return Result.ok(records);
    }

    @Override
    public Result<Article> getArticleById(Long id) {
        // article:list:{id}
        String key = "article:list:" + id;
        Article article = redisUtil.getObject(key);
        if (ObjectUtils.isEmpty(article)) {
            synchronized (GET_ARTICLE_BY_ID) {
                article = redisUtil.getObject(key);
                if (ObjectUtils.isEmpty(article)) {
                    article = articleMapper.selectById(id);
                    queryCategoriesAndTagList(article);
                    if (!ObjectUtils.isEmpty(article)) {
                        redisUtil.setObject(key, article, 60 * 1000);
                    }
                }
            }
        }
        return Result.ok(article);
    }

    @Override
    public Result<List<Article>> queryArticleByUserId(Long userId) {
        // user:{userId}:articles
        String key = "user:" + userId + ":articles";
        List<Article> articles = redisUtil.getObject(key);
        if (CollectionUtils.isEmpty(articles)) {
            synchronized (QUERY_ARTICLE_BY_USER_ID) {
                articles = redisUtil.getObject(key);
                if (CollectionUtils.isEmpty(articles)) {
                    LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<Article>()
                            .select(Article.class, i -> !i.getProperty().equals("context"));
                    articles = articleMapper.selectList(queryWrapper);
                    if (!CollectionUtils.isEmpty(articles)) {
                        redisUtil.setObject(key, articles, 60 * 1000);
                    }
                }
            }
        }
        return Result.ok(articles);
    }

    @Override
    public Result<List<Article>> pageArticleByUserIdAndCategoriesId(Long userId, Long categoriesId, Long page, Long size) {
        // user:{userId}:categories:{categoriesId}:{page}
        String key = "user:" + userId + ":categories:" + categoriesId + ":" + page;
        List<Article> records = redisUtil.getObject(key);
        if (CollectionUtils.isEmpty(records)) {
            synchronized (PAGE_ARTICLE_BY_USER_ID_AND_CATEGORIES_ID) {
                records = redisUtil.getObject(key);
                if (CollectionUtils.isEmpty(records)) {
                    LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
                    queryWrapper.eq(Article::getUserId, userId).eq(Article::getCategoriesId, categoriesId);
                    records = getArticleList(page, size, key, queryWrapper);
                }
            }
        }

        return Result.ok(records);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Article> saveArticle(Article article) {
        String categoriesName = article.getCategories().getName();
        List<String> tagNames = article.getTagList().stream().map(Tag::getName).collect(Collectors.toList());

        LambdaQueryWrapper<Categories> categoriesLambdaQueryWrapper = new LambdaQueryWrapper<>();
        categoriesLambdaQueryWrapper.eq(Categories::getName, categoriesName);
        Categories categories = categoriesMapper.selectOne(categoriesLambdaQueryWrapper);
        if (categories == null) {
            categories = new Categories();
            categories.setName(categoriesName);
            categoriesMapper.insert(categories);
        }
        article.setCategoriesId(categories.getId());

        List<Tag> tagList = new ArrayList<>();
        tagNames.forEach(tagName -> {
            LambdaQueryWrapper<Tag> tagLambdaQueryWrapper = new LambdaQueryWrapper<>();
            tagLambdaQueryWrapper.eq(Tag::getName, tagName);
            Tag tag = tagMapper.selectOne(tagLambdaQueryWrapper);
            if (tag == null) {
                tag = new Tag();
                tag.setName(tagName);
                tagMapper.insert(tag);
            }
            tagList.add(tag);
        });
        articleMapper.insert(article);
        redisUtil.deleteLikeObject("*" + article.getUserId() + "*");
        tagList.forEach(tag -> tagMapper.insertArticleTag(IdWorker.getId(), article.getId(), tag.getId()));
        article.setTagList(tagList);

        return Result.ok(article);
    }

    @NotNull
    private List<Article> getArticleList(Long page, Long size, String key, LambdaQueryWrapper<Article> queryWrapper) {
        List<Article> records;
        IPage<Article> articlePage = articleMapper.selectPage(new Page<>(page, size), queryWrapper);
        records = articlePage.getRecords();
        records.forEach(this::queryCategoriesAndTagList);
        if (!CollectionUtils.isEmpty(records)) {
            redisUtil.setObject(key, records, 60 * 1000);
        }
        return records;
    }

    private void queryCategoriesAndTagList(Article article) {
        article.setCategories(categoriesMapper.selectById(article.getCategoriesId()));
        article.setTagList(tagMapper.queryTagByArticleId(article.getId()));
    }

}
